package com.jphilipp.android.powertracker;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.text.Html;
import android.util.Base64;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.TextView;

import com.dsi.ant.AntDefine;
import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import com.dsi.ant.exception.AntServiceNotConnectedException;

public class PowerTrackerActivity extends Activity implements AntInterface.ServiceListener,
        SensorEventListener, LocationListener {

    public static final String LOG_TAG = PowerTrackerActivity.class.getSimpleName();

    public Writer logFileWriter = null;

    public void log(String message) {
        if (debug) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

            String fullClassName = stackTrace[3].getClassName();
            String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
            String methodName = stackTrace[3].getMethodName();
            int lineNumber = stackTrace[3].getLineNumber();

            String tag = className + "." + methodName + "():" + lineNumber;

            if (debugToSystem) {
                Log.d(tag, message);
            }

            if (debugToFile && (logFileWriter != null)) {
                try {
                    logFileWriter.write(tag + ":" + message + "\r\n");
                    logFileWriter.flush();
                } catch (IOException e) {
                }
            }
        }
    }

    public void log(Exception e) {
        log(e.toString());
    }

    protected boolean started = false;

    protected WakeLock partialWakeLock = null;
    protected WakeLock fullWakeLock = null;

    SensorManager sensorManager = null;
    protected Sensor pressureSensor = null;
    protected Sensor accelerationSensor = null;

    protected LocationManager locationManager = null;

    protected AntInterface antInterface = null;

    protected ViewGroup mainView = null;
    protected GridView displayGridView = null;
    protected Button trackButton = null;

    public static class LapStats {
        public long duration;
        public double distance;
        public double altitudeMin;
        public double altitudeMax;
        public double altitudeGain;
        public double altitudeLoss;
        public double speedAvg;
        public double powerAvg;
        public double cadenceAvg;
        public double heartrateAvg;
    }

    public double pressureSum;
    public double heartrateSum;
    public double powerSum;
    public double cadenceSum;
    public double wheelrpmSum;
    public double ascentAvg;
    public double descentAvg;

    public static class DataUnit {
        public long time;
        public double latitude;
        public double longitude;
        public double altitude;
        public double speed;
        public double pressure;
        public double acceleration;
        public double heartrate;
        public double cadence;
        public double wheelrpm;
        public double power;
    }

    protected ArrayList<ArrayList<DataUnit>> prevDataUnits = new ArrayList<ArrayList<DataUnit>>();
    protected ArrayList<LapStats> prevLapStats = new ArrayList<LapStats>();

    protected ArrayList<DataUnit> dataUnits = new ArrayList<DataUnit>();
    protected LapStats lapStats = new LapStats();

    protected double[] accelerationBuffer = new double[4096];
    protected int accelerationCount = 0;

    protected Location[] locationBuffer = new Location[16];
    protected int locationCount = 0;

    protected float[] pressureBuffer = new float[4096];
    protected int pressureCount = 0;

    protected static final byte HEART_RATE_CHANNEL_NUMBER = 0;
    protected static final byte CADENCE_SPEED_CHANNEL_NUMBER = 1;
    protected static final byte POWER_CHANNEL_NUMBER = 2;

    protected static final byte HEART_RATE_DEVICE_TYPE = 120;
    protected static final short HEART_RATE_MESSAGE_PERIOD = 8070;
    protected static final byte CADENCE_SPEED_DEVICE_TYPE = 123;
    protected static final short CADENCE_SPEED_MESSAGE_PERIOD = 8086;
    protected static final byte POWER_DEVICE_TYPE = 11;
    protected static final short POWER_MESSAGE_PERIOD = 8182;
    protected static final byte POWER_MAIN_PAGE_NUMBER = 0x10;

    protected static final byte RF_CHANNEL_FREQ = 57;

    public static final int ANT_DATA_BUFFER_SIZE = 4096;
    protected int[] heartrateBuffer = new int[ANT_DATA_BUFFER_SIZE];
    protected int heartrateCount = 0;
    protected int[] powerBuffer = new int[ANT_DATA_BUFFER_SIZE];
    protected int[] cadenceBuffer = new int[ANT_DATA_BUFFER_SIZE];
    protected int powerCount = 0;
    protected int[] crankRevolutionsBuffer = new int[ANT_DATA_BUFFER_SIZE];
    protected int[] wheelRevolutionsBuffer = new int[ANT_DATA_BUFFER_SIZE];
    protected int cadenceSpeedCount = 0;

    protected final int DEVICE_NUMBER_WILDCARD = 0;

    protected SharedPreferences preferences = null;

    Handler handler = new Handler();

    Runnable updateRunnable = new Runnable() {
        public void run() {
            DataUnit data = new DataUnit();

            data.time = System.currentTimeMillis();

            // ////////////////////////////////////////
            // location
            // ////////////////////////////////////////
            if (locationCount > 0) {
                Location location = locationBuffer[locationCount - 1];
                data.latitude = location.getLatitude();
                data.longitude = location.getLongitude();
                data.altitude = location.getAltitude();
                data.speed = location.getSpeed();

                // distance += data.speed * (UPDATE_DATA_PERIOD / 1000);

                // if ((dataUnits.size() > 0) && ((dataUnits.size() % 5) == 0))
                // {
                // if ((data.speed * 3.6) < 25) {
                // beep(getApplicationContext(), 1);
                // } else if ((data.speed * 3.6) < 30) {
                // beep(getApplicationContext(), 2);
                // } else {
                // beep(getApplicationContext(), 3);
                // }
                // }

                if (locationCount >= 2) {
                    locationBuffer[0] = locationBuffer[locationCount - 1];
                    locationCount = 1;
                }
                // else {
                // locationCount = 0;
                // }
            } else {
                data.latitude = -1;
                data.longitude = -1;
                data.altitude = -1;
                data.speed = -1;
            }

            // ////////////////////////////////////////
            // heartrate
            // ////////////////////////////////////////
            if (heartrateCount > 0) {
                for (int i = 0; i < heartrateCount; i++) {
                    int hr = heartrateBuffer[i];
                    data.heartrate += hr;
                }
                data.heartrate /= heartrateCount;

                if (heartrateCount >= 2) {
                    heartrateBuffer[0] = heartrateBuffer[heartrateCount - 1];
                    heartrateCount = 1;
                } else {
                    heartrateCount = 0;
                }
            } else {
                data.heartrate = -1;
            }

            // ////////////////////////////////////////
            // power
            // ////////////////////////////////////////
            if (powerCount > 0) {
                for (int i = 0; i < powerCount; i++) {
                    int power = powerBuffer[i];
                    int cadence = cadenceBuffer[i];
                    data.power += power;
                    data.cadence += cadence;
                }
                data.power /= powerCount;
                data.cadence /= powerCount;

                if (powerCount >= 2) {
                    powerBuffer[0] = powerBuffer[powerCount - 1];
                    cadenceBuffer[0] = cadenceBuffer[powerCount - 1];
                    powerCount = 1;
                } else {
                    powerCount = 0;
                }
            } else {
                data.power = -1;
                data.cadence = -1;
            }

            // ////////////////////////////////////////
            // cadence
            // ////////////////////////////////////////
            if (cadenceSpeedCount >= 2) {
                data.cadence = crankRevolutionsBuffer[cadenceSpeedCount - 1]
                        - crankRevolutionsBuffer[0] * 60;

                if (cadenceSpeedCount > 2) {
                    crankRevolutionsBuffer[1] = crankRevolutionsBuffer[cadenceSpeedCount - 1];
                    cadenceSpeedCount = 2;
                } else {
                    cadenceSpeedCount = 0;
                }
            } else {
                data.cadence = -1;
            }

            // ////////////////////////////////////////
            // pressure
            // ////////////////////////////////////////
            if (pressureCount > 0) {
                for (int i = 0; i < pressureCount; i++) {
                    float pressure = pressureBuffer[i];
                    data.pressure += pressure;
                }
                data.pressure /= pressureCount;

                if (pressureCount >= 2) {
                    pressureBuffer[0] = pressureBuffer[pressureCount - 1];
                    pressureCount = 1;
                } else {
                    // pressureCount = 0;
                }
            } else {
                data.pressure = -1.0;
            }

            // ////////////////////////////////////////
            // acceleration
            // ////////////////////////////////////////
            if (accelerationCount > 0) {
                for (int i = 0; i < accelerationCount; i++) {
                    double acceleration = accelerationBuffer[i];
                    data.acceleration += acceleration;
                }
                data.acceleration /= accelerationCount;

                if (accelerationCount >= 2) {
                    accelerationBuffer[0] = accelerationBuffer[accelerationCount - 1];
                    accelerationCount = 1;
                } else {
                    accelerationCount = 0;
                }
            } else {
                data.acceleration = -1.0;
            }

            // log(String.format("new data unit (time=%d, lati=%.2f, longi=%.2f, "
            // +
            // "alti=%.2f, speed=%.2f, pressure=%.2f, acceleration=%.2f, heartrate=%.2f, "
            // + "cadence=%.2f, wheelrpm=%.2f, power=%.2f",
            // data.time, data.latitude, data.longitude, data.altitude,
            // data.speed, data.pressure, data.acceleration,
            // data.heartrate, data.cadence, data.wheelrpm, data.power));

            dataUnits.add(data);

            updateStats();

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    displayGridAdapter.notifyDataSetChanged();
                }
            });

            if ((dataUnits.size() % trackingFactor) == 0) {
                startOrUpdateTracking();
            }

            handler.postDelayed(this, updatePeriod);
        }
    };

    protected void updateStats() {
        DataUnit lastDataUnit = null;
        DataUnit prevDataUnit = null;
        DataUnit oldDataUnit = null;
        double lastPressureAvg = 0, newPressureAvg = 0;

        if (dataUnits.size() > 0) {
            lastDataUnit = dataUnits.get(dataUnits.size() - 1);

            lastPressureAvg = pressureSum / smoothingFactor;
            if (lastDataUnit.pressure != -1) {
                pressureSum += lastDataUnit.pressure;
            }

            if (lastDataUnit.heartrate != -1) {
                heartrateSum += lastDataUnit.heartrate;
            }

            if (lastDataUnit.cadence != -1) {
                cadenceSum += lastDataUnit.cadence;
            }

            if (lastDataUnit.power != -1) {
                powerSum += lastDataUnit.power;
            }

            if (lastDataUnit.wheelrpm != -1) {
                wheelrpmSum += lastDataUnit.wheelrpm;
            }
        }

        if (dataUnits.size() > smoothingFactor) {
            oldDataUnit = dataUnits.get(dataUnits.size() - smoothingFactor);

            if (oldDataUnit.pressure != -1) {
                pressureSum -= oldDataUnit.pressure;
            }
            newPressureAvg = pressureSum / smoothingFactor;

            if (lastPressureAvg > newPressureAvg) {
                ascentAvg = (lastPressureAvg - newPressureAvg) * 8;
                lapStats.altitudeGain += ascentAvg;
            } else {
                descentAvg = (newPressureAvg - lastPressureAvg) * 8;
                lapStats.altitudeGain -= descentAvg;
            }

            if (oldDataUnit.heartrate != -1) {
                heartrateSum -= oldDataUnit.heartrate;
            }

            if (oldDataUnit.cadence != -1) {
                cadenceSum -= oldDataUnit.cadence;
            }

            if (oldDataUnit.power != -1) {
                powerSum -= oldDataUnit.power;
            }

            if (oldDataUnit.wheelrpm != -1) {
                wheelrpmSum -= oldDataUnit.wheelrpm;
            }
        }

        if (dataUnits.size() > 1) {
            prevDataUnit = dataUnits.get(dataUnits.size() - 2);

            if ((lastDataUnit.latitude != -1) && (lastDataUnit.longitude != -1)
                    && (prevDataUnit.latitude != lastDataUnit.latitude)
                    && (prevDataUnit.longitude != lastDataUnit.longitude)) {
                float[] distance = new float[1];
                Location.distanceBetween(prevDataUnit.latitude, prevDataUnit.longitude,
                        lastDataUnit.latitude, lastDataUnit.longitude, distance);
                lapStats.distance += distance[0];
            }
        }
    }

    protected static final int ALTITUDE_POSITION = 0;
    protected static final int DISTANCE_POSITION = 1;
    protected static final int ASCENT_POSITION = 2;
    protected static final int ALTITUDE_GAIN_POSITION = 3;
    protected static final int ACCELERATION_POSITION = 4;
    protected static final int SPEED_POSITION = 5;
    protected static final int POWER_POSITION = 6;
    protected static final int CADENCE_POSITION = 7;

    protected static final int COUNT_POSITION = 8;

    BaseAdapter displayGridAdapter = new BaseAdapter() {

        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = new TextView(getApplicationContext());
                ((TextView) convertView).setGravity(Gravity.CENTER);
                ((TextView) convertView).setTextSize(16);
                ((TextView) convertView)
                        .setBackgroundResource(R.drawable.gallery_unselected_default);
            }
            if (dataUnits.size() > 0) {
                DataUnit dataUnit = dataUnits.get(dataUnits.size() - 1);

                switch (position) {
                case ALTITUDE_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "altitude<br/><b>%.2f m</b>", dataUnit.altitude)));
                    break;
                case DISTANCE_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "distance<br/><b>%.2f m</b>", lapStats.distance)));
                    break;
                case ASCENT_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "ascent<br/><b>%.2f m/h</b>", ascentAvg * 3600)));
                    break;
                case ALTITUDE_GAIN_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "gain<br/><b>%.2f m</b>", lapStats.altitudeGain)));
                    break;
                case ACCELERATION_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "acceleration<br/><b>%.2f m/s2</b>", dataUnit.acceleration)));
                    break;
                case SPEED_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "speed<br/><b>%.2f km/h</b>",
                            (wheelrpmSum / smoothingFactor * 60 * 2.1 / 1000))));
                    break;
                case POWER_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "power<br/><b>%.2f w</b>", powerSum / smoothingFactor)));
                    break;
                case CADENCE_POSITION:
                    ((TextView) convertView).setText(Html.fromHtml(String.format(
                            "cadence<br/><b>%.2f rpm</b>", cadenceSum / smoothingFactor)));
                    break;
                }
            }
            return convertView;
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public boolean isEnabled(int position) {
            return false;
        }

        public int getCount() {
            return COUNT_POSITION;
        }

        public Object getItem(int position) {
            return null;
        }

        public long getItemId(int position) {
            return 0;
        }

    };

    protected void initLogFile() {
        log("initializing log file");
        
        if (logFileWriter == null) {
            GregorianCalendar currentCalendar = new GregorianCalendar();
            File logDir = new File(Environment.getExternalStorageDirectory() + "/PowerTracker");
            logDir.mkdirs();
            File logFile = new File(logDir, String.format("%04d%02d%02d%02d%02d%02d.log",
                    currentCalendar.get(Calendar.YEAR), currentCalendar.get(Calendar.MONTH)
                            - Calendar.JANUARY + 1, currentCalendar.get(Calendar.DAY_OF_MONTH),
                    currentCalendar.get(Calendar.HOUR_OF_DAY), currentCalendar.get(Calendar.MINUTE),
                    currentCalendar.get(Calendar.SECOND)));
            try {
                logFileWriter = new BufferedWriter(new FileWriter(logFile));
            } catch (IOException e) {
                log(e);
            }
        }
    }

    protected void finiLogFile() {
        log("finalizing log file");        
        
        if (logFileWriter != null) {
            try {
                logFileWriter.close();
            } catch (IOException e) {
                log(e);
            }
            logFileWriter = null;
        }
    }

    protected void initUpdateTask() throws Exception {
        log("initializing update task");
        
        handler.removeCallbacks(updateRunnable);
        handler.postDelayed(updateRunnable, updatePeriod);
    }

    protected void finiUpdateTask() throws Exception {
        log("finalizing update task");
        
        handler.removeCallbacks(updateRunnable);
    }

    protected void initGps() throws Exception {
        log("initializing gps");
        
        if (locationManager == null) {
            locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                    gpsMinTime, gpsMinDist, this);
        }
    }

    protected void finiGps() throws Exception {
        log("finalizing gps");
        
        if (locationManager != null) {
            locationManager.removeUpdates(this);
            locationManager = null;
        }
    }

    protected void initAccelerationSensor() throws Exception {
        log("initializing acceleration sensor");
        
        if (accelerationSensor == null) {
            accelerationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            sensorManager.registerListener(this, accelerationSensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    protected void finiAccelerationSensor() throws Exception {
        log("finalizing acceleration sensor");
        
        if (accelerationSensor != null) {
            sensorManager.unregisterListener(this, accelerationSensor);
            accelerationSensor = null;
        }
    }

    protected void initPressureSensor() throws Exception {
        log("initializing pressure sensor");
        
        if (pressureSensor == null) {
            pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
            sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    protected void finiPressureSensor() throws Exception {
        log("finalizing pressure sensor");
        
        if (pressureSensor != null) {
            sensorManager.unregisterListener(this, pressureSensor);
            pressureSensor = null;
        }
    }

    protected void initMainView() throws Exception {
        log("initializing main view");
        
        if (mainView == null) {
            LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
            mainView = (ViewGroup) inflater.inflate(R.layout.main, null);
            displayGridView = (GridView) mainView.findViewById(R.id.data_grid);
            trackButton = (Button) mainView.findViewById(R.id.track_button);
            trackButton.setText("Start");
            trackButton.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
                    if (started) {
                        started = false;
                        trackButton.setEnabled(false);
                        stopTracking();
                    } else {
                        trackButton.setEnabled(false);
                        nextDataUnit = dataUnits.size() - 1;
                        started = true;
                    }
                }
            });
            displayGridView.setAdapter(displayGridAdapter);
            setContentView(mainView);
        }
    }

    protected void finiMainView() throws Exception {
        log("finalizing main view");
        
        if (mainView != null) {
            setContentView(null);        
            mainView = null;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (debug && debugToFile) {
            initLogFile();
        }

        // getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        // getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON|WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        // setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        try {
            initMainView();
        } catch (Exception e) {
            log(e);
        }

        if (enableAnt) {
            try {
                initAnt();
            }
            catch (Exception e) {
                log(e);
            }
        }

        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        if (enableAcceleration) {
            try {
                initAccelerationSensor();
            }
            catch (Exception e) {
                log(e);
            }
        }

        if (enablePressure) {
            try {
                initPressureSensor();
            }
            catch (Exception e) {
                log(e);
            }
        }

        if (enableGps) {
            try {
                initGps();
            }
            catch (Exception e) {
                log(e);
            }
        }

        try {
            initUpdateTask();
        }
        catch (Exception e) {
            log(e);
        }

        if (enableTracking) {
            log("get time offset from tracking server");
            getTimeOffset();
        }
    }
    
    @Override
    protected void onDestroy() {
        try {
            finiUpdateTask();
        }
        catch (Exception e) {
            log(e);
        }

        if (enableGps) {
            try {
                finiGps();
            }
            catch (Exception e) {
                log(e);
            }
        }

        if (enableAcceleration) {
            try {
                finiAccelerationSensor();
            }
            catch (Exception e) {
                log(e);
            }
        }
        
        if (enablePressure) {
            try {
                finiPressureSensor();
            }
            catch (Exception e) {
                log(e);
            }
        }

        if (enableAnt) {  
            try {
                finiAnt();
            }
            catch (Exception e) {
                log(e);
            }
        }
        
        try {
            finiMainView();
        }
        catch (Exception e) {
            log(e);
        }

        if (debug && debugToFile) {
            finiLogFile();
        }
        
        super.onDestroy();
    }

    protected void onResume() {
        super.onResume();

//        log("acquire wake locks");
//        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
//        partialWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PowerTracker");
//        fullWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK
//                | PowerManager.ON_AFTER_RELEASE | PowerManager.ACQUIRE_CAUSES_WAKEUP,
//                "PowerTracker");
    }

    protected void onPause() {
//        log("release wake locks");
//        if (partialWakeLock != null) {
//            if (partialWakeLock.isHeld()) {
//                partialWakeLock.release();
//                partialWakeLock = null;
//            }
//        }
//        if (fullWakeLock != null) {
//            if (fullWakeLock.isHeld()) {
//                fullWakeLock.release();
//                fullWakeLock = null;
//            }
//        }

        super.onPause();
    }

    private final BroadcastReceiver antStatusReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context ctx, Intent intent) {
            String action = intent.getAction();

            log("new status intent: " + action);

            if (antInterface != null) {
                if (action.equals(AntInterfaceIntent.ANT_INTERFACE_CLAIMED_ACTION)) {
                    try {
                        if (antInterface.hasClaimedInterface()) {
                            log("ant interface claimed");

                            if (!antInterface.isEnabled()) {
                                log("enabling ant interface");
                                antInterface.enable();
                            } else {
                                log("ant interface already enabled");

                                log("resetting ant interface");
                                antInterface.ANTResetSystem();
                            }
                        } else {
                            log("ant interface claimed by another application!");
                        }
                    } catch (AntInterfaceException e) {
                        log("exception: " + e);
                    }
                } else if (action.equals(AntInterfaceIntent.ANT_ENABLED_ACTION)) {
                    log("ant interface enabled");

                    try {
                        log("onReceive: resetting ant interface");
                        antInterface.ANTResetSystem();
                    } catch (AntInterfaceException e) {
                        log("exception: " + e);
                    }
                } else if (action.equals(AntInterfaceIntent.ANT_RESET_ACTION)) {
                    log("ant interface reset");

                    try {
                        log("opening ant channels");
                        setupAntChannels();
                    } catch (AntInterfaceException e) {
                        log("exception: " + e);
                    }
                }
            }
        }
    };

    private final BroadcastReceiver antDataReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context ctx, Intent intent) {
            String action = intent.getAction();

            log("ant data intent: " + action);

            if (antInterface != null) {
                if ((action != null) && action.equals(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION)) {
                    byte[] message = intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
                    if (message != null) {
                        log("ant rx message: " + bytesToString(message));
                        try {
                            processAntRxMessage(message);
                        } catch (Exception e) {
                            log(e);
                        }
                    }
                }
            }
        }
    };

    protected void initAnt() throws Exception {
        log("initializing ant");
        
        if (antInterface == null) {
            if (!AntInterface.hasAntSupport(this)) {
                throw new Exception("no ant support");
            } else {
                log("registering ant broadcast listeners");
                IntentFilter statusIntentFilter = new IntentFilter();
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_ENABLING_ACTION);
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_DISABLING_ACTION);
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_RESET_ACTION);
                statusIntentFilter.addAction(AntInterfaceIntent.ANT_INTERFACE_CLAIMED_ACTION);
                statusIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
                registerReceiver(antStatusReceiver, statusIntentFilter);

                IntentFilter dataIntentFilter = new IntentFilter();
                dataIntentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
                registerReceiver(antDataReceiver, dataIntentFilter);

                log("requesting ant interface");
                antInterface = AntInterface.getInstance(this, this);
                if (antInterface == null) {
                    throw new Exception("no ant support");
                }
            }
        }
    }

    protected void finiAnt() throws Exception {
        log("finalizing ant");
        
        try {
            unregisterReceiver(antStatusReceiver);
            unregisterReceiver(antDataReceiver);
        } catch (IllegalArgumentException e) {
            // not an error
        }

        if (antInterface != null) {
            antInterface.releaseInterface();
            antInterface.destroy();
            antInterface = null;
        }
    }

    public void onServiceConnected() {
        log("ant service connected");

        if (antInterface != null) {
            try {
                if (!antInterface.hasClaimedInterface()) {
                    log("claiming ant interface");

                    if (!antInterface.claimInterface()) {
                        log("cannot claim ant interface");
                    }
                } else {
                    log("ant interface already claimed");

                    if (!antInterface.isEnabled()) {
                        log("enabling ant interface");
                        antInterface.enable();
                    } else {
                        log("ant interface already enabled");

                        log("resetting ant interface");
                        antInterface.ANTResetSystem();
                    }
                }
            } catch (AntInterfaceException e) {
                log("exception: " + e);
            }
        }
    }

    public void onServiceDisconnected() {
        log("ant service disconnected");
    }

    protected void setupAntChannels() throws AntInterfaceException {
        if (enableHeartRate) {
            log("setup channel for hr");
            openAntChannel(HEART_RATE_CHANNEL_NUMBER, heartRateDeviceNumber,
                    HEART_RATE_DEVICE_TYPE, HEART_RATE_MESSAGE_PERIOD);
        }

        if (enableCadenceSpeed) {
            log("setup channel for cs");
            openAntChannel(CADENCE_SPEED_CHANNEL_NUMBER, cadenceSpeedDeviceNumber,
                    CADENCE_SPEED_DEVICE_TYPE, CADENCE_SPEED_MESSAGE_PERIOD);
        }

        if (enablePower) {
            log("setup channel for pw");
            openAntChannel(POWER_CHANNEL_NUMBER, powerDeviceNumber, POWER_DEVICE_TYPE,
                    POWER_MESSAGE_PERIOD);
        }
    }

    protected void openAntChannel(byte channelNumber, short deviceNumber, byte deviceType,
            short messagePeriod) throws AntInterfaceException {
        if (antInterface != null) {
            // 0 == public, 1 == ANT+, 2 == ANTFS
            antInterface
                    .ANTAssignChannel(channelNumber, AntDefine.PARAMETER_RX_NOT_TX, (byte) 0x01);

            antInterface.ANTSetChannelId(channelNumber, deviceNumber, deviceType, (byte) 0);
            antInterface.ANTSetChannelPeriod(channelNumber, messagePeriod);
            antInterface.ANTSetChannelRFFreq(channelNumber, RF_CHANNEL_FREQ);

            // no high priority search
            antInterface.ANTSetChannelSearchTimeout(channelNumber, (byte) 0);

            // search timeout: 30 seconds
            antInterface.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12);

            if (deviceNumber == DEVICE_NUMBER_WILDCARD) {
                // enable proximity search
                antInterface.ANTSetProximitySearch(channelNumber, (byte) 7);
            }

            antInterface.ANTOpenChannel(channelNumber);
        }
    }

    protected void closeAntChannel(byte channelNumber) throws AntInterfaceException {
        if (antInterface != null) {
            antInterface.ANTCloseChannel(channelNumber);
            antInterface.ANTUnassignChannel(channelNumber);
        }
    }

    protected void processAntRxMessage(byte[] message) throws Exception {
        if ((message == null) || (message.length == 0)) {
            throw new Exception("empty message");
        }

        int messageSize = (int) message[AntMesg.MESG_SIZE_OFFSET] & 0xff;
        log(String.format("message size (messageSize: %02x)", messageSize));

        if ((messageSize != message.length - 2) || (message.length <= 2)) {
            throw new Exception("unexpected message size");
        }

        byte messageId = message[AntMesg.MESG_ID_OFFSET];
        log(String.format("message id (messageId: %02x)", messageId));

        switch (messageId) {
        case AntMesg.MESG_STARTUP_MESG_ID: {
            log("startup message");
            break;
        }
        case AntMesg.MESG_CHANNEL_ID_ID: {
            if (message.length < 7) {
                throw new Exception("unexpected message size");
            }

            byte channelNumber = message[AntMesg.MESG_DATA_OFFSET];
            short deviceNumber = getAntShort(message[AntMesg.MESG_DATA_OFFSET + 1],
                    message[AntMesg.MESG_DATA_OFFSET + 2]);
            byte deviceType = message[AntMesg.MESG_DATA_OFFSET + 3];
            byte transmissionType = message[AntMesg.MESG_DATA_OFFSET + 4];

            log(String
                    .format("channel id message "
                            + "(channelNumber: %02x, deviceNumber: %02x, deviceType: %02x, transmissionType: %02x)",
                            channelNumber, deviceNumber, deviceType, transmissionType));

            switch (channelNumber) {
            case HEART_RATE_CHANNEL_NUMBER:
                heartRateDeviceNumber = deviceNumber;
                break;
            case CADENCE_SPEED_CHANNEL_NUMBER:
                cadenceSpeedDeviceNumber = deviceNumber;
                break;
            case POWER_CHANNEL_NUMBER:
                powerDeviceNumber = deviceNumber;
                break;
            }
            savePreferences();

            try {
                antInterface.ANTSetChannelId(channelNumber, deviceNumber, deviceType,
                        transmissionType);
            } catch (AntInterfaceException e) {
                log("exception: " + e);
            }

            break;
        }
        case AntMesg.MESG_ACKNOWLEDGED_DATA_ID:
        case AntMesg.MESG_BROADCAST_DATA_ID: {
            if (message.length < AntMesg.MESG_DATA_OFFSET + 1) {
                throw new Exception("unexpected message size");
            }

            byte channelNumber = message[AntMesg.MESG_DATA_OFFSET];
            log(String.format("acknowledged/broadcast data (channelNumber: %02x)", channelNumber));

            switch (channelNumber) {
            case HEART_RATE_CHANNEL_NUMBER:
                processAntHeartRateData(message);
                break;
            case CADENCE_SPEED_CHANNEL_NUMBER:
                processAntCadenceSpeedData(message);
                break;
            case POWER_CHANNEL_NUMBER:
                processAntPowerData(message);
                break;
            }
        }
        case AntMesg.MESG_RESPONSE_EVENT_ID: {
            if (message.length < AntMesg.MESG_DATA_OFFSET + 5) {
                throw new Exception("unexpected message size");
            }

            byte channelNumber = message[2];
            byte eventId = message[3];
            byte eventCode = message[4];

            log(String.format("handleMessage: response event message "
                    + "(channelNumber: %02x, eventId: %02x, eventCode: %02x)", channelNumber,
                    eventId, eventCode));

            if ((eventId == AntMesg.MESG_EVENT_ID)
                    && (eventCode == AntDefine.EVENT_RX_SEARCH_TIMEOUT)) {
                log(String.format("search timeout on channel (channelNumber: %02x", channelNumber));
                closeAntChannel(channelNumber);
            }
        }
            break;
        }
    }

    protected void processAntHeartRateData(byte[] message) throws Exception {
        if (message.length < AntMesg.MESG_DATA_OFFSET + 9) {
            throw new Exception("unexpected message size");
        }

        int heartRate = (int) message[AntMesg.MESG_DATA_OFFSET + 8] & 0xff;
        log("heart rate data (heartRate: " + heartRate + ")");

        heartrateBuffer[heartrateCount] = heartRate;
        heartrateCount = (heartrateCount + 1) % ANT_DATA_BUFFER_SIZE;

        if (heartRateDeviceNumber == DEVICE_NUMBER_WILDCARD) {
            log("requesting heart rate device number");
            try {
                antInterface.ANTRequestMessage(HEART_RATE_CHANNEL_NUMBER,
                        AntMesg.MESG_CHANNEL_ID_ID);
            } catch (AntInterfaceException e) {
                log(e);
            }
        }
    }

    protected void processAntCadenceSpeedData(byte[] message) throws Exception {
        if (message.length < AntMesg.MESG_DATA_OFFSET + 9) {
            throw new Exception("unexpected message size");
        }

        int crankRevolutions = ((int) message[AntMesg.MESG_DATA_OFFSET + 3] & 0xff)
                + ((int) message[AntMesg.MESG_DATA_OFFSET + 4] & 0xff) * 256;
        int wheelRevolutions = ((int) message[AntMesg.MESG_DATA_OFFSET + 7] & 0xff)
                + ((int) message[AntMesg.MESG_DATA_OFFSET + 8] & 0xff) * 256;
        log("cadence/speed data (crankRevolutions: " + crankRevolutions + ", wheelRevolutions: "
                + wheelRevolutions + ")");

        crankRevolutionsBuffer[cadenceSpeedCount] = crankRevolutions;
        wheelRevolutionsBuffer[cadenceSpeedCount] = wheelRevolutions;
        cadenceSpeedCount = (cadenceSpeedCount + 1) % ANT_DATA_BUFFER_SIZE;

        if (cadenceSpeedDeviceNumber == DEVICE_NUMBER_WILDCARD) {
            log("requesting cadence/speed device number");
            try {
                antInterface.ANTRequestMessage(CADENCE_SPEED_CHANNEL_NUMBER,
                        AntMesg.MESG_CHANNEL_ID_ID);
            } catch (AntInterfaceException e) {
                log(e);
            }
        }
    }

    protected void processAntPowerData(byte[] message) throws Exception {
        byte pageNumber = message[AntMesg.MESG_DATA_OFFSET + 1];
        log(String.format("page number (pageNumber: %02x)", pageNumber));

        if (pageNumber == POWER_MAIN_PAGE_NUMBER) {
            if (message.length < AntMesg.MESG_DATA_OFFSET + 9) {
                throw new Exception("unexpected message size");
            }

            int cadence = (int) message[AntMesg.MESG_DATA_OFFSET + 4] & 0xff;
            int power = ((int) message[AntMesg.MESG_DATA_OFFSET + 7] & 0xff)
                    + ((int) message[AntMesg.MESG_DATA_OFFSET + 8] & 0xff) * 256;
            log("power data main page (cadence: " + cadence + ", power: " + power + ")");

            powerBuffer[powerCount] = power;
            cadenceBuffer[powerCount] = cadence;
            powerCount = (powerCount + 1) % ANT_DATA_BUFFER_SIZE;
        }

        if (powerDeviceNumber == DEVICE_NUMBER_WILDCARD) {
            log("requesting power device number");
            try {
                antInterface.ANTRequestMessage(POWER_CHANNEL_NUMBER, AntMesg.MESG_CHANNEL_ID_ID);
            } catch (AntInterfaceException e) {
                log(e);
            }
        }
    }

    protected String bytesToString(byte[] message) {
        StringBuilder out = new StringBuilder();
        for (byte b : message) {
            out.append(String.format("%s%02x", (out.length() == 0 ? "" : " "), b));
        }
        return out.toString();
    }

    protected static short getAntShort(byte b0, byte b1) {
        int value = b0 & 0xFF;
        value |= (b1 & 0xFF) << 8;
        return (short) value;
    }

    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        if (sensor == pressureSensor) {
            log("accuracy changed for pressure sensor (" + accuracy + ")");
        }
    }

    // protected final double TAP_THRESHOLD = 20.0;
    // protected final long DOUBLE_TAP_DELAY = 500;
    // protected long last_tap_time = -1;

    public void onSensorChanged(SensorEvent event) {
        if ((event.sensor != null) && (event.sensor == pressureSensor) && (event.values != null)
                && (event.values.length > 0)) {
            float pressure = event.values[0];
            // log(String.format("new pressure value (%.2f)", pressure));

            pressureBuffer[pressureCount] = pressure;
            pressureCount = (pressureCount + 1) % pressureBuffer.length;
        } else if ((event.sensor != null) && (event.sensor == accelerationSensor)
                && (event.values != null) && (event.values.length == 3)) {
            float accelerationX = event.values[SensorManager.DATA_X];
            float accelerationY = event.values[SensorManager.DATA_Y];
            float accelerationZ = event.values[SensorManager.DATA_Z];
            // log(String.format("new acceleration values:  (%.2f, %.2f, %.2f)",
            // accelerationX, accelerationY, accelerationZ));

            double acceleration = Math.abs(accelerationX) + Math.abs(accelerationY)
                    + Math.abs(accelerationZ);
            // log(String.format("acceleration %.2f", acceleration));
            // if (acceleration > TAP_THRESHOLD) {
            // long time = System.currentTimeMillis();
            // if ((last_tap_time > 0) && ((time - last_tap_time) <
            // DOUBLE_TAP_DELAY)) {
            // if (fullWakeLock != null) {
            // fullWakeLock.acquire();
            // fullWakeLock.release();
            // }
            // }
            // last_tap_time = time;
            // }

            accelerationBuffer[accelerationCount] = acceleration;
            accelerationCount = (accelerationCount + 1) % accelerationBuffer.length;
        }
    }

    public void onLocationChanged(Location location) {
        log("location changed (" + location + ")");

        locationBuffer[locationCount] = location;
        locationCount = (locationCount + 1) % locationBuffer.length;
    }

    public void onProviderDisabled(String provider) {
        log("provider (" + provider + ")");
    }

    public void onProviderEnabled(String provider) {
        log("provider (" + provider + ")");
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
        log("provider (" + provider + ") status (" + status + ")");
    }

    protected MediaPlayer beepPlayer = null;
    protected boolean beepPlaying = false;
    protected int beepCount = 0;
    protected static final int BEEP_PERIOD = 150;

    protected final Runnable beepRunnable = new Runnable() {
        public void run() {
            if (beepPlayer != null) {
                beepPlayer.start();
            }
        }
    };

    public void beep(Context context, int count) {

        if (beepPlayer == null) {
            beepPlayer = MediaPlayer.create(this, R.raw.beep);
            beepPlayer.setLooping(false);
            beepPlayer.setOnCompletionListener(new OnCompletionListener() {
                public void onCompletion(MediaPlayer player) {
                    beepCount -= 1;
                    if (beepCount <= 0) {
                        beepPlaying = false;
                    } else {
                        handler.postDelayed(beepRunnable, BEEP_PERIOD);
                    }
                }
            });
        }

        if (!beepPlaying) {
            beepPlaying = true;
            beepCount = count;
            handler.postDelayed(beepRunnable, BEEP_PERIOD);
        }
    }

    protected long timeOffset = 0;

    protected void getTimeOffset() {
        handler.post(new Runnable() {
            public void run() {
                HttpClient httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost("http://www.mapmytracks.com/api/");

                try {
                    List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
                    nameValuePairs.add(new BasicNameValuePair("request", "get_time"));
                    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                    String credentials = trackingUser + ":" + trackingPass;
                    String encoded = new String(Base64.encode(credentials.getBytes(),
                            Base64.NO_WRAP));
                    httpPost.addHeader("Authorization", "Basic " + encoded);

                    HttpResponse response = httpClient.execute(httpPost);

                    Reader reader = new InputStreamReader(response.getEntity().getContent());
                    StringWriter writer = new StringWriter();
                    char[] buffer = new char[1024];
                    int n = reader.read(buffer);
                    writer.write(buffer, 0, n);
                    String xml = writer.toString();

                    log("getTimeOffset: response received (" + xml + ")");

                    int idx1 = xml.indexOf("<server_time>");
                    int idx2 = xml.indexOf("</server_time>");
                    if ((idx1 != -1) && (idx2 != -1)) {
                        String serverTimeStr = xml.substring(idx1 + 13, idx2);
                        log("serverTime=" + serverTimeStr);
                        try {
                            long serverTime = Long.parseLong(serverTimeStr);
                            timeOffset = (System.currentTimeMillis() / 1000) - serverTime;
                            log("timeOffset=" + timeOffset);
                        } catch (NumberFormatException e) {
                            log("could not parse serverTime");
                        }
                    }

                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    protected String activityId = null;

    protected static final int MAX_DATA_UNITS = 100;
    protected int nextDataUnit = 0;

    protected boolean updating = false;

    protected void startOrUpdateTracking() {
        if (started && !updating) {
            updating = true;

            handler.post(new Runnable() {
                public void run() {
                    HttpClient httpClient = new DefaultHttpClient();
                    HttpPost httpPost = new HttpPost("http://www.mapmytracks.com/api/");

                    try {
                        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();

                        if (activityId != null) {
                            nameValuePairs
                                    .add(new BasicNameValuePair("request", "update_activity"));
                            nameValuePairs.add(new BasicNameValuePair("activity_id", activityId));
                        } else {
                            nameValuePairs.add(new BasicNameValuePair("request", "start_activity"));
                            nameValuePairs.add(new BasicNameValuePair("title", "Test Tracking 1"));
                            nameValuePairs.add(new BasicNameValuePair("activity", "cycling"));
                            nameValuePairs.add(new BasicNameValuePair("privacy", "public"));
                            nameValuePairs.add(new BasicNameValuePair("source", "PowerTracker"));
                            nameValuePairs.add(new BasicNameValuePair("version", "0.1"));
                        }

                        StringBuffer points = new StringBuffer();
                        StringBuffer hr = new StringBuffer();
                        StringBuffer cad = new StringBuffer();
                        StringBuffer pwr = new StringBuffer();

                        int updateCount = dataUnits.size() - nextDataUnit;
                        if (updateCount > MAX_DATA_UNITS) {
                            updateCount = MAX_DATA_UNITS;
                        }

                        log("adding " + updateCount + " data units");

                        for (int i = 0; i < updateCount; i++) {
                            DataUnit dataUnit = dataUnits.get(nextDataUnit);
                            DataUnit prevDataUnit = null;
                            if (nextDataUnit > 0) {
                                prevDataUnit = dataUnits.get(nextDataUnit - 1);
                            }
                            if ((dataUnit.latitude != -1) && (dataUnit.longitude != -1)
                                    && (dataUnit.altitude != -1)) {
                                if ((prevDataUnit == null)
                                        || (prevDataUnit.latitude != dataUnit.latitude)
                                        || (prevDataUnit.longitude != dataUnit.longitude)
                                        || (prevDataUnit.altitude != dataUnit.altitude)) {
                                    points.append(dataUnit.latitude);
                                    points.append(" ");
                                    points.append(dataUnit.longitude);
                                    points.append(" ");
                                    points.append(dataUnit.altitude);
                                    points.append(" ");
                                    points.append((dataUnit.time / 1000) - timeOffset);
                                    points.append(" ");
                                }
                            }
                            if (dataUnit.heartrate != -1) {
                                hr.append(dataUnit.heartrate);
                                hr.append(" ");
                                hr.append((dataUnit.time / 1000) - timeOffset);
                                hr.append(" ");
                            }
                            if (dataUnit.cadence != -1) {
                                cad.append(dataUnit.cadence);
                                cad.append(" ");
                                cad.append((dataUnit.time / 1000) - timeOffset);
                                cad.append(" ");
                            }
                            if (dataUnit.power != -1) {
                                pwr.append(dataUnit.power);
                                pwr.append(" ");
                                pwr.append((dataUnit.time / 1000) - timeOffset);
                                pwr.append(" ");
                            }
                            nextDataUnit += 1;
                        }

                        if (points.length() > 0) {
                            points.deleteCharAt(points.length() - 1);
                            // log("points (" + points + ")");
                            nameValuePairs.add(new BasicNameValuePair("points", points.toString()));

                            if (hr.length() > 0) {
                                hr.deleteCharAt(hr.length() - 1);
                                nameValuePairs.add(new BasicNameValuePair("hr", hr.toString()));
                            }
                            if (cad.length() > 0) {
                                cad.deleteCharAt(cad.length() - 1);
                                nameValuePairs.add(new BasicNameValuePair("cad", cad.toString()));
                            }
                            if (pwr.length() > 0) {
                                pwr.deleteCharAt(pwr.length() - 1);
                                nameValuePairs.add(new BasicNameValuePair("pwr", pwr.toString()));
                            }
                            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                            String credentials = trackingUser + ":" + trackingPass;
                            String encoded = new String(Base64.encode(credentials.getBytes(),
                                    Base64.NO_WRAP));
                            httpPost.addHeader("Authorization", "Basic " + encoded);

                            HttpResponse response = httpClient.execute(httpPost);

                            Reader reader = new InputStreamReader(response.getEntity().getContent());
                            StringWriter writer = new StringWriter();
                            char[] buffer = new char[1024];
                            int n = reader.read(buffer);
                            writer.write(buffer, 0, n);
                            String xml = writer.toString();

                            log("response received (" + xml + ")");

                            if (activityId == null) {
                                int idx1 = xml.indexOf("<activity_id>");
                                int idx2 = xml.indexOf("</activity_id>");
                                if ((idx1 != -1) && (idx2 != -1)) {
                                    activityId = xml.substring(idx1 + 13, idx2);
                                    log("activityId found (" + activityId + ")");
                                    runOnUiThread(new Runnable() {
                                        public void run() {
                                            trackButton.setText("Stop");
                                            trackButton.setEnabled(true);
                                        }
                                    });
                                }
                            }
                        } else {
                            log("no gps location");
                        }

                    } catch (ClientProtocolException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    updating = false;
                }
            });
        }
    }

    protected void stopTracking() {
        handler.post(new Runnable() {
            public void run() {
                HttpClient httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost("http://www.mapmytracks.com/api/");

                try {
                    List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
                    nameValuePairs.add(new BasicNameValuePair("request", "stop_activity"));
                    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                    String credentials = trackingUser + ":" + trackingPass;
                    String encoded = new String(Base64.encode(credentials.getBytes(),
                            Base64.NO_WRAP));
                    httpPost.addHeader("Authorization", "Basic " + encoded);

                    HttpResponse response = httpClient.execute(httpPost);

                    Reader reader = new InputStreamReader(response.getEntity().getContent());
                    StringWriter writer = new StringWriter();
                    char[] buffer = new char[1024];
                    int n = reader.read(buffer);
                    writer.write(buffer, 0, n);
                    String xml = writer.toString();

                    log("response received (" + xml + ")");

                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                activityId = null;

                runOnUiThread(new Runnable() {
                    public void run() {
                        trackButton.setText("Start");
                        trackButton.setEnabled(true);
                    }

                });
            }
        });
    }

    public boolean debug = true;
    public boolean debugToSystem = true;
    public boolean debugToFile = true;

    public boolean lockScreen = false;

    public long updatePeriod = 1000;
    public int smoothingFactor = 5;

    public boolean enableGps = true;
    public long gpsMinTime = 10000;
    public int gpsMinDist = 10;

    public long beepingPeriod = 150;

    public boolean enableTracking = true;
    public String trackingUser = "jphilipp";
    public String trackingPass = "testaccount";
    public int trackingFactor = 10;
    public int trackingMaxPoints = 100;

    public boolean enableAnt = true;
    public boolean enableHeartRate = false;
    protected short heartRateDeviceNumber = 0;
    public boolean enableCadenceSpeed = true;
    protected short cadenceSpeedDeviceNumber = 0;
    public boolean enablePower = false;
    protected short powerDeviceNumber = 0;

    public boolean enableAcceleration = false;

    public boolean enablePressure = true;

    public static final int DISPLAY_TYPE_NONE = -1;
    public static final int DISPLAY_TYPE_POWER = 0;
    public static final int DISPLAY_TYPE_POWER_AVERAGE = 1;
    public static final int DISPLAY_TYPE_SPEED = 2;
    public static final int DISPLAY_TYPE_SPEED_AVERAGE = 3;
    public static final int DISPLAY_TYPE_HEARTRATE = 4;
    public static final int DISPLAY_TYPE_HEARTRATE_AVERAGE = 5;
    public static final int DISPLAY_TYPE_CADENCE = 6;
    public static final int DISPLAY_TYPE_CADENCE_AVERAGE = 7;
    public static final int DISPLAY_TYPE_DISTANCE = 8;
    public static final int DISPLAY_TYPE_DURATION = 9;
    public static final int DISPLAY_TYPE_ALTITUDE = 10;
    public static final int DISPLAY_TYPE_ASCENT = 11;
    public static final int DISPLAY_TYPE_DESCENT = 12;
    public static final int DISPLAY_TYPE_TRACKING_STATUS = 13;
    public static final int DISPLAY_TYPE_KILOJOULES = 14;
    public static final int DISPLAY_TYPE_GPS_STATUS = 15;
    public static final int DISPLAY_TYPE_TSS = 16;
    public static final int DISPLAY_TYPE_IF = 17;
    public static final int DISPLAY_TYPE_ASCENT_RATE = 18;
    public static final int DISPLAY_TYPE_DESCENT_RATE = 19;

    public int[] displayTypes = new int[] { DISPLAY_TYPE_POWER, DISPLAY_TYPE_SPEED,
            DISPLAY_TYPE_CADENCE, DISPLAY_TYPE_HEARTRATE, DISPLAY_TYPE_DISTANCE,
            DISPLAY_TYPE_DURATION, DISPLAY_TYPE_ALTITUDE, DISPLAY_TYPE_ASCENT,
            DISPLAY_TYPE_ASCENT_RATE, DISPLAY_TYPE_KILOJOULES };

    public void initPreferences() {
        log("init preferences");
        preferences = getPreferences(MODE_PRIVATE);
    }

    public void finiPreferences() {
        log("fini preferences");
    }

    public void loadPreferences() {
        log("loading preferences");
        if (preferences != null) {
            heartRateDeviceNumber = (short) preferences.getInt("heartRateDeviceNumber",
                    DEVICE_NUMBER_WILDCARD);
            cadenceSpeedDeviceNumber = (short) preferences.getInt("cadenceSpeedDeviceNumber",
                    DEVICE_NUMBER_WILDCARD);
            powerDeviceNumber = (short) preferences.getInt("powerDeviceNumber",
                    DEVICE_NUMBER_WILDCARD);
        }
    }

    public void savePreferences() {
        log("saving preferences");
        if (preferences != null) {
            Editor editor = preferences.edit();
            editor.putInt("heartRateDeviceNumber", (int) heartRateDeviceNumber);
            editor.putInt("cadenceSpeedDeviceNumber", (int) cadenceSpeedDeviceNumber);
            editor.putInt("powerDeviceNumber", (int) powerDeviceNumber);
            editor.commit();
        }
    }
}